/*
 * Levels.h : load and helper function to render maps from Minecraft alpha/beta.
 *
 * Written by ZomBuster, arranged by T.Pierron.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SIT.h"
#include "NBT.h"
#include "Level.h"
#include "graphics.h"
#include "Cartograph.h"

extern Options opt;
extern Mutex serial, sync;
static Level level;

Level LEVEL_Init(void)
{
	if (!level) {
		level = LEVEL_New();
		LEVEL_GetTexture(level);
	}
	return level;
}

Level LEVEL_New(void)
{
	Level lev = calloc(sizeof *lev, 1);

	lev->mapx = 16;
	lev->mapy = 16;
	lev->mapz = 128;

	/* Default block colors */
	static ULONG block[] = {
		RGBA(255,255,255,0),   RGBA(120,120,120,255), RGBA(117,176,73,255),  RGBA(134,96,67,255),   // Air, Stone, Grass, Dirt
		RGBA(115,115,115,255), RGBA(157,128,79,255),  RGBA(120,120,120,0),   RGBA(84,84,84,255),    // Coble, Wood, Sapling, Bedrock
		RGBA(38,92,255,51),    RGBA(38,92,255,51),    RGBA(255,90,0,255),    RGBA(255,90,0,255),    // Water, Water, Lava, Lava
		RGBA(218,210,158,255), RGBA(136,126,126,255), RGBA(143,140,125,255), RGBA(136,130,127,255), // Sand, Gravel, Gold, Iron
		RGBA(115,115,115,255), RGBA(102,81,51,255),   RGBA(60,192,41,100),   RGBA(165,165,50,255),  // Coal, Tree, Leave, Sponge
		RGBA(255,255,255,64),  RGBA(83,97,131,255),   RGBA(25,63,155,255),   RGBA(106,106,106,255), // Glass, LP ore, LP Blk, Disp
		RGBA(218,210,158,255), RGBA(105,67,48,255),   0,                     0,                     // SandSt, Note
		0,                     0,                     0,                     0,
		0,                     0,                     0,                     RGBA(222,222,222,255), // Wool
		0,                     RGBA(255,255,0,255),   RGBA(255,0,0,255),     0,                     // Flower, Rose, Mush
		RGBA(209,84,18,255),   RGBA(231,165,45,255),  RGBA(191,191,191,255), RGBA(200,200,200,255), // Mush, Gold, Iron, Stone
		RGBA(200,200,200,255), RGBA(169,86,62,255),   RGBA(160,83,65,255),   RGBA(147,119,73,255),  // Stone, Brick, TNT, Book
		RGBA(101,130,101,255), RGBA(26,11,43,255),    RGBA(245,220,50,200),  RGBA(255,169,30,200),  // Moss, Obsi, Torch, Fire
		RGBA(101,98,207,255),  RGBA(157,128,79,255),  RGBA(125,91,38,255),   RGBA(245,220,50,200),  // Spaner, WStairs, Chest, Wire
		RGBA(129,140,143,255), RGBA(45,166,152,255),  RGBA(114,88,56,255),   RGBA(146,192,0,255),   // Diam, Diam, WB, Crops
		RGBA(95,58,30,255),    RGBA(96,96,96,255),    RGBA(96,96,96,255),    RGBA(111,91,54,255),   // Farm, Furnace, Furn, Sign
		RGBA(136,109,67,255),  RGBA(181,140,64,32),   RGBA(150,134,102,180), RGBA(115,115,115,255), // WDoor, Ladder, Track, Strair
		RGBA(111,91,54,255),   0,                     0,                     RGBA(200,200,200,255), // Sign, IDoor
		RGBA(157,128,79,255),  RGBA(131,107,107,255), RGBA(131,107,107,255), RGBA(181,140,64,32),   // WPlate, RSOre, RSOre, RSTorch
		RGBA(255,0,0,200),     0,                     RGBA(255,255,255,255), RGBA(83,113,163,51),   // RSTorch, Snow, Ice
		RGBA(250,250,250,255), RGBA(25,120,25,255),   RGBA(151,157,169,255), RGBA(193,234,150,255), // Snow, Catus, Clay, Sugar
		RGBA(105,67,48,255),   RGBA(157,128,79,255),  RGBA(176,109,19,255),  RGBA(152,50,48,255),   // Juke, Fence, Pump, Nether
		RGBA(78,59,48,255),    RGBA(245,220,50,200),  RGBA(102,61,144,255),  RGBA(228,255,0,255),   // SoulSand, GlowSt, Portal, Pump
	};

	static ULONG wool[] = {
		RGBA(0xde,0xde,0xde,255), RGBA(0xea,0x7f,0x37,255), RGBA(0xbf,0x4b,0xc9,255), RGBA(0x68,0x8b,0xd4,255),
		RGBA(0xc2,0xb5,0x1c,255), RGBA(0x3b,0xbd,0x30,255), RGBA(0xd9,0x83,0x9b,255), RGBA(0x42,0x42,0x42,255),
		RGBA(0x9e,0xa6,0xa6,255), RGBA(0x27,0x75,0x95,255), RGBA(0x81,0x36,0xc4,255), RGBA(0x27,0x33,0x9a,255),
		RGBA(0x56,0x33,0x1c,255), RGBA(0x38,0x4d,0x18,255), RGBA(0xa4,0x2d,0x29,255), RGBA(0x1b,0x17,0x17,255)
	};
	static ULONG wood[] = {
		RGBA(0x66,0x51,0x32,255), RGBA(0x2e,0x1d,0x0c,255), RGBA(0xcf,0xce,0xc9,255)
	};

	memcpy(lev->blockC, block, sizeof block);
	memcpy(lev->woolC, wool, sizeof wool);
	memcpy(lev->woodC, wood, sizeof wood);

	return lev;
}

void Blend(DATA8 dst /* A */, ULONG col /* B */, int h)
{
	float Aa = dst[3] / 255.;
	float Ba = RGB_GETA(col) / 255.;
	float Alpha = Aa + Ba*(1-Aa);
	dst[2] = (dst[2]*Aa + (RGB_GETR(col)*h/128.)*Ba*(1-Aa))/Alpha;
	dst[1] = (dst[1]*Aa + (RGB_GETG(col)*h/128.)*Ba*(1-Aa))/Alpha;
	dst[0] = (dst[0]*Aa + (RGB_GETB(col)*h/128.)*Ba*(1-Aa))/Alpha;
	h = Alpha*255; if (h > 255) h = 255;
	dst[3] = h;
}

ULONG Mix(ULONG c1, ULONG c2, double ratio)
{
	if (RGB_GETA(c2) == 0) return c1;
	int a = RGB_GETA(c1);
	c2 = RGB_SETA(c2, 255.0*ratio);
	c1 = RGB_SETA(c1, 255.0*(1-ratio));
	Blend((DATA8)&c1,c2,128);
	return RGB_SETA(c1, a);
}


/* Get texture colors according to texture file from minecraft */
void LEVEL_GetTexture(Level lev)
{
	Image   img = GFX_LoadImage("raw://texture.png");
	Image   wlf = GFX_LoadImage("raw://WLF.png"); // Water, Lava, Fire
	DATA8   pixels;
	int     x, y, sz;

	if (img == NULL) return;

	if (img->width != 256) /* Hi-Res Texture pack */
	{
		Image ViewImageReduce(Image source, Image ret, Rect * from, Rect * to);
		Image niou = ViewImageReduce(img, GFX_CreateImage(256, 256, img->bpp), NULL, NULL);
		GFX_FreeImage(img);
		img = niou;
	}

	sz = img->width / 16;
	pixels = img->bitmap;
	for (x = 0; x < 16; x++) // patch of 16x16 textures
	{
		for (y = 0; y < 16; y++)
		{
			int avr = 0;
			int avg = 0;
			int avb = 0;
			int ava = 0;
			int count = 0;
			int xx, yy, n = x+y*sz;

			for (xx = 0; xx < sz; xx++)
			{
				for (yy = 0; yy < sz; yy++)
				{
					DATA8 p = pixels + (x * sz + xx) * 4 + (y * sz + yy) * img->stride;

					if (p[3] > 0)
						avr += p[0], avg += p[1], avb += p[2], ava += p[3], count++;

					lev->texture[n][xx+yy*sz + 1] = RGBA(p[0], p[1], p[2], p[3]);
				}
			}
			lev->texture[n][0] = RGBA(avr/count, avg/count, avb/count, ava/count);
		}
	}

	pixels = wlf->bitmap;
	sz = wlf->width;

	for(y = 0; y < 3; y++)
	{
		int avr = 0;
		int avg = 0;
		int avb = 0;
		int ava = 0;
		int count = 0;
		int xx, yy;

		for (xx = 0; xx < sz; xx++)
		{
			for (yy = 0; yy < sz; yy++)
			{
				DATA8 p = pixels + (xx * 4) + (y * sz + yy) * wlf->stride;

				if (p[3] > 0)
					avr += p[0], avg += p[1], avb += p[2], ava += p[3], count++;

				lev->texture[253+y][xx+yy*16 + 1] = RGBA(p[0], p[1], p[2], p[3]);
			}
		}
		lev->texture[253+y][0] = RGBA(avr/count, avg/count, avb/count, ava/count);
	}
	GFX_FreeImage(img);
	GFX_FreeImage(wlf);
}

int LEVEL_IDToTerrain(int blockid)
{
	static int location[] = {
		 0,  1,   0,   2, 16,   4, -15,  17, 253, 253, 254, 254,   18, 19, 32, 33,
		34, 20,  53,  48, 49, 160, 144,  46, 176,  74,   0,   0,    0,  0,  0,  0,
		 0,  0,   0,  64,  0,  13,  12, -29, -28,  23,  22,   5,    6,  7,  9, 35,
		36, 37,  80, 255, 65,   4,  27,  84,  50,  24,  43, -95,   87, 44, 61,  0,
		97, 83, 128,  16,  0, -96,   0,  98,   0,  51,  51, -99, -115,  0, 66, 67,
		66, 70,  72,  73, 75,   4, 118, 103, 104, 105,   0, 120, -121
	};
	/* Map blockid into index within terrain.png file (which also has 256 slots) */
	if (0 <= blockid && blockid < DIM(location)) return location[blockid];
	return 0;
}

ULONG LEVEL_GetColor(Level lev, int blockid, int dat)
{
	static int woolloc[] = { /* Wool location according to color (Beta 1.2) */
		64, 210, 194, 178, 162, 146, 130, 114, 225, 209, 193, 177, 161, 145, 129, 113
	};
	static int trunk[] = { /* Trunk texture (Beta 1.2) */
		20, 116, 117
	};
	static int stoneslab[] = { /* Half step texture (Beta 1.3) */
		6, 176, 4, 16
	};
	int index;

	switch (blockid) {
	case 17:   index = trunk[dat%3]; break;
	case 35:   index = woolloc[dat]; break; /* Wool */
	case 43:   index = stoneslab[dat & 3]; break; /* Double Stone Slab */
	case 44:   index = stoneslab[dat & 3]; break; /* Half step */
	case 50:   return RGBA(255, 255, 50, 255); /* Torch */
	case 76:   return RGBA(255,   0,  0, 255); /* RS torch */
	case 2:    index = 0; break;
	case 1003: index = 3; break; /* Grass from side */
	default:   index = LEVEL_IDToTerrain(blockid);
	           if (index <= 0) return RGBA(0,0,0,0);
	}

	ULONG A = lev->texture[index][0];

	A = RGB_SETA(A, RGB_GETA(lev->blockC[blockid]));
	ULONG B = lev->texture[index][(rand() & 0xff) + 1];
	return Mix(A, B, opt.tex/8);
}

static long base36todec(STRPTR * start)
{
	STRPTR p = *start;
	long   n = 0;
	int    s = 1;

	if (*p == '-') s = -1, p ++;

	for (;;) {
		int digit = '0' <= *p && *p <= '9' ? *p - '0' :
		            'a' <= *p && *p <= 'z' ? *p - 'a' + 10 :
					'A' <= *p && *p <= 'Z' ? *p - 'A' + 10 : -1;

		if (digit < 0) break;
		n = n * 36 + digit;
		p ++;
	}
	if (*p == '.') p ++;
	*start = p;
	return n * s;
}

static Chunk LEVEL_AddFile(ListHead * first, STRPTR path)
{
	// A chunk file from the level
	STRPTR file = BaseName(path);

	if (StrCount(file, '.') == 3)
	{
		Chunk c = malloc(sizeof *c + strlen(path));

		strcpy(c->file, path);

		base36todec(&file); // First letter is "c" for "chunk".
		c->x = base36todec(&file);
		c->y = base36todec(&file);
		ListAddTail(first, &c->node);
		return c;
	}
	return NULL;
}

int LEVEL_ScanDir(STRPTR dir, STRPTR file, APTR ud)
{
	if (strcmp(file, "DIM-1") == 0) return 0; /* Nether */

	AddPart(dir, file, 512);

	if (IsDir(dir))
		ScanDir(dir, LEVEL_ScanDir, ud);
	else
		LEVEL_AddFile(ud, dir);
	ParentDir(dir);
	return 1;
}

#define SZ_HDR      4096

int LEVEL_ScanRegion(STRPTR dir, STRPTR file, APTR ud)
{
	int x, z, i;
	AddPart(dir, file, 512);
	if (! IsDir(dir) && sscanf(file, "r.%d.%d.mcr", &x, &z) == 2)
	{
		/* Read header to get chunk availability */
		FILE * in = fopen(dir, "rb");
		if (in)
		{
			DATA8 header = malloc(SZ_HDR), p;
			if (fread(header, 1, SZ_HDR, in) == SZ_HDR)
			{
				for (i = 0, p = header; i < SZ_HDR; i += 4, p += 4)
				{
					if (* (uint32_t *) p)
					{
						Chunk c = LEVEL_AddFile(ud, file);

						c->offset = ((((p[0] << 8) | p[1]) << 8) | p[2]) * SZ_HDR;
						c->x = x * 32 + ((i >> 2) & 31);
						c->y = z * 32 + (i >> 7);
					}
				}
			}
			free(header);
			fclose(in);
		}
	}
	ParentDir(dir);
	return 1;
}

/* Scan directory for all files on disk for a given level */
Bool LEVEL_GetFiles(ListHead * files, STRPTR base)
{
	TEXT path[512];

	CopyString(path, base, sizeof path);
	AddPart(path, "region", sizeof path);

	if (IsDir(path))
	{
		/* Beta 1.3: regions of 32x32 chunks */
		ScanDir(path, LEVEL_ScanRegion, files);
	}
	else /* Old format: one file per chunk */
	{
		ParentDir(path);
		ScanDir(path, LEVEL_ScanDir, files);
	}
	return True;
}

void LEVEL_GetRect(ListHead * files, int * rect)
{
	Chunk file = HEAD(*files);

	if (file)
	{
		rect[0] = rect[2] = file->x;
		rect[1] = rect[3] = file->y;
		rect[4] = 100000000; rect[6] = -100000000;
		rect[5] = 100000000; rect[7] = -100000000;

		for (NEXT(file); file; NEXT(file))
		{
			if (file->x < rect[0]) rect[0] = file->x;
			if (file->y < rect[1]) rect[1] = file->y;
			if (file->x > rect[2]) rect[2] = file->x;
			if (file->y > rect[3]) rect[3] = file->y;
		}

		if (opt.mode == OBLIQUE_ANGLED || opt.mode == ISOMETRIC)
		{
			for (file = HEAD(*files); file; NEXT(file))
			{
				int xx = file->x;
				int yy = file->y;

				if (opt.flip == 1)
					yy = abs((file->y)-(rect[3]-rect[1]));
				if (opt.rotate == 0)
					xx = abs((file->x)-(rect[2]-rect[0]));

				int x7 = xx - yy;
				int y7 = xx + yy;
				if (x7 < rect[4]) rect[4] = x7;
				if (y7 < rect[5]) rect[5] = y7;
				if (x7 > rect[6]) rect[6] = x7;
				if (y7 > rect[7]) rect[7] = y7;
			}
		}
	}
	else memset(rect, 0, sizeof *rect * 8);
}

int LEVEL_SortFiles(ListNode * node1, ListNode * node2)
{
	#define chunk1     ((Chunk)node1)
	#define chunk2     ((Chunk)node2)
	if (opt.mode == OBLIQUE_ANGLED || opt.mode == ISOMETRIC)
	{
		int xx  = chunk1->x - chunk1->y;
		int yy  = chunk1->x + chunk1->y;
		int xx2 = chunk2->x - chunk2->y;
		int yy2 = chunk2->x + chunk2->y;

		if (opt.flip == 1 && opt.rotate == 1) return xx2 - xx;
		if (opt.flip == 1) return yy - yy2;
		if (opt.rotate == 1) return yy2 - yy;
		return xx - xx2;
	}
	else
	{
		if (opt.flip == 1)
		{
			if (opt.rotate == 0)
				return chunk1->y - chunk2->y;
			else
				return chunk1->x - chunk2->x;
		}
		else
		{
			if (opt.rotate == 0)
				return chunk2->y - chunk1->y;
			else
				return chunk2->x - chunk1->x;
		}
	}
}

Image LEVEL_AllocImage(int * rect)
{
	int w, h;

	if (opt.mode == OBLIQUE_ANGLED || opt.mode == ISOMETRIC)
	{
		int cxx = (rect[6] - rect[4]);
		int cyy = (rect[7] - rect[5]);

		w = cxx*16 + 32;
		h = cyy*16 + 304;
		if (opt.mode == ISOMETRIC)
		{
			w = cxx*32 + 512;
			h = cyy*16 + 512;
		}
	}
	else
	{
		w = (rect[2] - rect[0])*16 + 16;
		h = (rect[3] - rect[1])*16 + 16  + (opt.mode == OBLIQUE)*128;
		if (opt.rotate) swap(w, h);
	}
	fprintf(stderr, "allocating image %dx%dpx\n", w, h);
	return GFX_CreateImage(w, h, 32);
}

int LEVEL_Read(DATA8 data, int x, int y, int z, int def, int pack)
{
	if (x >= 16  || x < 0) return def;
	if (y >= 16  || y < 0) return def;
	if (z >= 128 || z < 0) return def;
	def = x*128 + y*128*16 + z;
	if (pack) return def & 1 ? data[def>>1] >> 4 : data[def>>1] & 0xf;
	else      return data[def];
}

/* Check for transparent block */
Bool LEVEL_IsBlock(DATA8 data, int x, int y, int z)
{
	static char isBlock[] = {
		1,0,0,0,0,0,1,0,1,1,1,0,0,0,0,0,
		0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,
		0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,1,
		1,1,1,0,1,1,1,1,1,0,0,1,1,1,1,1,
		0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0
	};
	int b = LEVEL_Read(data, x, y, z, 0, 0);

	return 0 <= b && b < DIM(isBlock) ? isBlock[b] : 0;
}

double LEVEL_GetLight(Level lev, int x, int y, int z, double sky, double block)
{
	double b = LEVEL_Read(lev->skylight,x,y,z,15*sky,1)*sky;
	double s = LEVEL_Read(lev->blocklight,x,y,z,0,1)*block;

	if (!opt.nowater && opt.mode == NORMAL) s = 15*sky;
	if (s > b)
		return s+1;
	else
		return b+1;
}

void LEVEL_LoadFrom(Level lev, NBT root, int cut, Image map)
{
	DATA8 blocklight, skylight, blocks;
	int   i, x, y, z;
	int   full = 16*16*128;

	if (root == NULL) return;

	NBT node = NBT_FindNode(root, "Blocks");
	blocks = node ? node->val.string : NULL;
	node = NBT_FindNode(root, "SkyLight");
	skylight = node ? node->val.string : NULL;
	node = NBT_FindNode(root, "BlockLight");
	blocklight = node ? node->val.string : NULL;

	lev->skylight = skylight;
	lev->blocklight = blocklight;
	node = NBT_FindNode(root, "Data");
	lev->data = node ? node->val.string : NULL;

	/* Count number of blocks type */
	for(i = 0; i < full; lev->count[blocks[i]] ++, i++);

	/* Count mob entities */
	node = NBT_FindNode(root, "Entities");
	if (node && node->type == TAG_List_Compound)
	{
		for (i = 0; i < node->len; i ++)
		{
			NBT entity = node->val.array[i];
			NBT id     = NBT_FindNode(entity, "id");

			if (id && id->type == TAG_String) {
				x = FindInList("Pig,Cow,Sheep,Squid,Zombie,Skeleton,Creeper,Slime", id->val.string, 0);
				if (x >= 0) lev->mobs[x] ++;
			}
		}
	}

	switch (opt.mode) {
	case NORMAL:
	case HEIGHTMAP:
	case HEIGHTGRAY:
	case SLICE:
		for (x = 0; x < 16; x++)
		{
			for (y = 0; y < 16; y++)
			{
				int l  = lev->posx+y;
				int rr = lev->posy+x;

				if (opt.flip) rr = map->height - rr - 1;
				if (opt.rotate) swap(rr, l);

				DATA8 t = &map->bitmap[(l + rr*map->width) * 4];

				if (opt.cave)
				{
					for(z = 127; z > 0; z--)
					{
						int blight = LEVEL_Read(blocklight, x, y, z, 0, 1);

						rr = LEVEL_Read(blocks, x, y, z, 0, 0);

						if (LEVEL_Read(skylight, x, y, z, 15, 1) == 0 && LEVEL_IsBlock(blocks, x, y, z))
						{
							ULONG B;

							if (blight > 0) blight++;

							if (LEVEL_Read(skylight, x, y, z, 15, 1) == 0 && LEVEL_IsBlock(blocks, x, y, z) && blight == 0)
								blight = 1;

							if (LEVEL_Read(skylight, x, y, z, 15, 1) != 0 || rr == 8 || rr == 9 || rr == 79)
								blight = 0;

							int bbb = 128-(blight*8);

							int rt = (bbb > 0)*(bbb <= 32)*255 + (bbb > 32)*(bbb < 64)*(32-(bbb-32))*8;
							int gt = (bbb > 96)*(bbb < 128)*(32-(bbb-96))*8 + (bbb > 0)*(bbb < 32)*(bbb)*8 + (bbb >= 32)*(bbb <= 96)*255;
							int bt = (bbb >= 96)*(bbb < 128)*255 + (bbb > 64)*(bbb < 96)*(bbb-64)*8;

							if (rr == 10 || rr == 11) // Lava
							{
								rt = 255;
								gt = 255;
								bt = 0;
							}

							B = RGBA(rt*2, gt*2, bt*2, (blight > 0)*255);

							int zop = z*0.5 + 64;

							Blend(t, B, zop);
							if (t[3] == 255) break;
						}
					}
				}
				else switch (opt.mode) {
				case NORMAL:
					for (z = 127; z >= 0; z--)
					{
						rr = LEVEL_Read(blocks, x, y, z, 0, 0);
						if (opt.nowater && (rr == 8 || rr == 9)) continue;
						if (rr != 0 && (opt.exclude == 0 || rr == opt.exclude))
						{
							switch (opt.daynight) {
							case 0: l = LEVEL_GetLight(lev, x, y, z+1, 1.0, 1)*4; break;
							case 1: l = LEVEL_GetLight(lev, x, y, z+1, 0.5, 1)*4; break;
							case 2: l = LEVEL_GetLight(lev, x, y, z+1, 0.0, 1)*4; break;
							}
							if (opt.exclude != 0) l = 128;
							int br = 0.8*l + 0.5*z; if (br > 128) br = 128;

							if (rr == 10 || rr == 11 || rr == 50 || rr == 51 || rr == 76) { /* Lava/Torch */
								Blend(t, LEVEL_GetColor(lev, rr, 0), 128);
							} else if ((rr == 8 || rr == 9 || rr == 79) && ! opt.nowater) { /* Water/ice */
								ULONG P = LEVEL_GetColor(lev, rr, 0);
								if (LEVEL_Read(blocks, x, y, z+1, 0, 0) == 0) {
									Blend(t, RGB_SETA(P, 128), br);
								}
							} else {
								Blend(t, LEVEL_GetColor(lev, rr, LEVEL_Read(lev->data, x, y, z, 0, 1)), br);
							}
						}
						if (t[3] >= 255) { t[3] = 255; break; }
					}
					break;

				case HEIGHTMAP:
					for (z = 127; z >= 0; z--)
					{
						rr = LEVEL_Read(blocks, x, y, z, 0, 0);

						if (rr != 0 && ((rr != 8 && rr != 9) || !opt.nowater) && (opt.exclude == 0 || rr == opt.exclude))
						{
							/* Map to HSV */
							int h = 128-z;
							t[2] = (h > 0)*(h <= 32)*255 + (h > 32)*(h < 64)*(32-(h-32))*8;
							t[1] = (h > 96)*(h < 128)*(32-(h-96))*8 + (h > 0)*(h < 32)*(h)*8 + (h >= 32)*(h <= 96)*255;
							t[0] = (h >= 96)*(h < 128)*255 + (h > 64)*(h < 96)*(h-64)*8;
							t[3] = 255;
							break;
						}
					}
					break;

				case HEIGHTGRAY:
					for (z = 127; z >= 0; z--)
					{
						rr = LEVEL_Read(blocks, x, y, z, 0, 0);
						if (rr != 0 && ((rr != 8 && rr != 9) || !opt.nowater) && (opt.exclude == 0 || rr == opt.exclude))
						{
							t[2] = z*2;
							t[1] = z*2;
							t[0] = z*2;
							t[3] = 255;
							break;
						}
					}
					break;
				#if 0
				case ORES:
					for(z = 127; z >= 0; z--)
					{
						int rr = LEVEL_Read(blocks, x, y, z, 0, 0);

						if (rr == 14) *t = Blend(*t,Color(255,128,0,32),128); /* Gold ore */
						if (rr == 15) *t = Blend(*t,Color(255,0,0,32),128);   /* Iron */
						if (rr == 16) *t = Blend(*t,Color(0,0,0,32),128);     /* Coal */
						if (rr == 21) *t = Blend(*t,Color(0,0,128,32),128);   /* Lazuli */
						if (rr == 56) *t = Blend(*t,Color(255,0,128,32),128); /* Diam */

						if (t->a > 255) { z = -10; t->a = 255; }
					}
					break;
				#endif
				case SLICE:
					rr = LEVEL_Read(blocks, x, y, opt.slice, 0, 0);
					if (rr != 0 && (opt.exclude == 0 || rr == opt.exclude))
					{
						ULONG col = LEVEL_GetColor(lev, rr, LEVEL_Read(lev->data, x, y, opt.slice, 0, 1));
						t[2] = RGB_GETR(col);
						t[1] = RGB_GETG(col);
						t[0] = RGB_GETB(col);
						t[3] = RGB_GETA(col);
					}
				}
			}
		}
		break;
	case OBLIQUE_ANGLED:
		if (lev->minH == NULL) {
			lev->minH = malloc(sizeof *lev->minH * map->width * 2);
			lev->curH = lev->minH + map->width;
			for (i = 0; i < map->width; i ++)
				lev->minH[i] = lev->curH[i] = map->height-1;
		}
		for (z = 128; z >= 0; z--)
		{
			TEXT waste[4];
			int xr, yr, dir = opt.rotate && !opt.flip;
			for (xr = 0; xr < 16; xr++)
			{
				for (yr = (1-dir)*15; yr != dir*17-1; yr += dir*2-1)
				{
					int xo, yo, ff = -1, ff2 = 1;

					if (opt.rotate)
					{
						if (!opt.flip)
						{
							yo =  yr;
							xo =  15-xr;
							ff = -1;
							ff2 = -1;
						} else {
							ff = 1;
							xo = xr;
						}
					} else {
						xo = 15 - xr;
					}

					if (opt.flip)
					{
						if (!opt.rotate)
						{
							xo = xr;
							yo = 15 - yr;
							ff = 1;
							ff2 = 1;
						} else {
							yo =  yr;
							ff2 = -1;
						}
					} else {
						yo = 15 - yr;
					}

					x = xr;
					y = yr;
					if (opt.rotate && !opt.flip)
						y =  15 - yr;

					int rr = LEVEL_Read(blocks, xo, yo, z, 0, 0);
					int dat = LEVEL_Read(lev->data, xo, yo, z, 0, 1);
					Bool f,t,r;
					f = LEVEL_IsBlock(blocks, xo-ff, yo, z);
					t = LEVEL_IsBlock(blocks, xo, yo, z+1);
					r = LEVEL_IsBlock(blocks, xo, yo-ff2, z);

					int rry = 128-z+16+y-x+lev->posy;
					int rrx = (y+x)+lev->posx;

					if (rry > lev->minH[rrx]) continue;
					if (rr == 0) {
						lev->curH[rrx] = rry+1;
						continue;
					}

					DATA8 t1, t2, t3, t4;
					t1 = &map->bitmap[(rrx+rry*map->width)*4];
					t2 = t1 + map->stride;
					t3 = t2 + 4;
					t4 = t1 + 4;

					if (rrx+1 >= map->width)  t4 = t3 = waste;
					if (rry+1 >= map->height) t2 = t3 = waste;

					int here,top,left,right;
					top = here = right = left = 128;
					if (opt.exclude == 0 && !opt.cave)
					{
						switch (opt.daynight) {
						case 0:
							here  = LEVEL_GetLight(lev,xo,yo,z,1,1)*4;
							top   = LEVEL_GetLight(lev,xo,yo,z+1,1,1)*4;
							left  = LEVEL_GetLight(lev,xo-ff,yo,z,1,1)*4;
							right = LEVEL_GetLight(lev,xo,yo-ff2,z,1,1)*4;
							break;
						case 1:
							here  = LEVEL_GetLight(lev,xo,yo,z,0.5,1)*4;
							top   = LEVEL_GetLight(lev,xo,yo,z+1,0.5,1)*4;
							left  = LEVEL_GetLight(lev,xo-ff,yo,z,0.5,1)*4;
							right = LEVEL_GetLight(lev,xo,yo-ff2,z,0.5,1)*4;
							break;
						case 2:
							here  = LEVEL_GetLight(lev,xo,yo,z,0,1)*4;
							top   = LEVEL_GetLight(lev,xo,yo,z+1,0,1)*4;
							left  = LEVEL_GetLight(lev,xo-ff,yo,z,0,1)*4;
							right = LEVEL_GetLight(lev,xo,yo-ff2,z,0,1)*4;
						}
					}
					if (opt.cave)
					{
						ULONG B;

						int blight = LEVEL_Read(blocklight,xo,yo,z,0,1);
						if (blight > 0) blight++;

						if (LEVEL_Read(skylight,xo,yo,z,15,1) == 0 && LEVEL_IsBlock(blocks,xo,yo,z) && blight == 0)
							blight = 1;

						if (LEVEL_Read(skylight,xo,yo,z,15,1) != 0 || rr == 8 || rr == 9 || rr == 79 || blight == 0)
							continue;

						int bbb = 128-(blight*8);

						int rt = (bbb > 0)*(bbb <= 32)*255 + (bbb > 32)*(bbb < 64)*(32-(bbb-32))*8;
						int gt = (bbb > 96)*(bbb < 128)*(32-(bbb-96))*8 + (bbb > 0)*(bbb < 32)*(bbb)*8 + (bbb >= 32)*(bbb <= 96)*255;
						int bt = (bbb >= 96)*(bbb < 128)*255 + (bbb > 64)*(bbb < 96)*(bbb-64)*8;

						if (rr == 10 || rr == 11)
						{
							rt = 255;
							gt = 255;
							bt = 0;
						}

						B = RGBA(rt*2, gt*2, bt*2, 255);

						int zop = (z>>1) + 64;

						Blend(t1,B,zop);
						Blend(t2,B,zop*0.5);
						Blend(t3,B,zop*0.65);
						Blend(t4,B,zop*0.8);
					}
					else /* !opt.cave */
					{
						if (rr != 0 && rr != 78 && (f || t ||  r || opt.exclude != 0) && (opt.exclude == 0 || rr == opt.exclude))
						{
							if (rr == 76 || rr == 75 || rr == 6 || rr == 37 || rr == 38 || rr == 39 || rr == 40 ||
							    rr == 51 || rr == 50 || rr == 59 || rr == 63 || rr == 65 || rr == 66)
							{
								if (rr == 50 || rr == 51 || rr == 76)
								{
									Blend(t2,LEVEL_GetColor(lev, rr, 0), 128);
								} else {
									Blend(t2,LEVEL_GetColor(lev, rr, 0), here*0.8+z*0.4);
								}
							} else {
								if (rr == 10 || rr == 11) { /* Lava */
									Blend(t1, LEVEL_GetColor(lev, rr, 0), 128);
									Blend(t2, LEVEL_GetColor(lev, rr, 0), 128*0.5);
									Blend(t3, LEVEL_GetColor(lev, rr, 0), 128*0.65);
									Blend(t4, LEVEL_GetColor(lev, rr, 0), 128*0.8);
								} else {
									if(rr == 8 || rr == 9 || rr == 79) /* Water */
									{
										if (opt.nowater) continue;
										ULONG P = LEVEL_GetColor(lev, rr, 0);
										if (LEVEL_Read(blocks,xo,yo,z+1,0,0)  == 0 ||
										    LEVEL_Read(blocks,xo+1,yo,z,-1,0) == 0 ||
										    LEVEL_Read(blocks,xo,yo+1,z,-1,0) == 0)
										{
											P = RGB_SETA(P, 128);
											Blend(t1,P,(here*0.8+z*0.4));
											Blend(t2,P,(here*0.8+z*0.4)*0.65);
											Blend(t3,P,(here*0.8+z*0.4)*0.5);
											Blend(t4,P,(here*0.8+z*0.4)*0.8);
										}
									}
									else if(rr == 2) /* Grass: special case */
									{
										if(LEVEL_Read(blocks,xo,yo,z+1,0,0) == 78) /* Snow covered */
											Blend(t1, RGBA(255,255,255,255),(top*0.8+z*0.4));
										else
											Blend(t1, LEVEL_GetColor(lev,2,0),(top*0.8+z*0.4));

										Blend(t2, LEVEL_GetColor(lev,3,0),(left*0.8+z*0.4)*0.65);
										Blend(t3, LEVEL_GetColor(lev,3,0),(right*0.8+z*0.4)*0.5);

										if(LEVEL_Read(blocks,xo,yo,z+1,0,0) == 78)
											Blend(t4,RGBA(255,255,255,255),(top*0.8+z*0.4)*0.8);
										else
											Blend(t4,LEVEL_GetColor(lev,2,0),(top*0.8+z*0.4)*0.8);
									}
									else /* Normal block */
									{
										ULONG col = LEVEL_GetColor(lev,rr,dat);
										if(LEVEL_Read(blocks,xo,yo,z+1,0,0) == 78) /* Snow covered */
											Blend(t1,RGBA(255,255,255,255),(top*0.8+z*0.4));
										else
											Blend(t1,col,(top*0.8+z*0.4));

										Blend(t2,col,(left*0.8+z*0.4)*0.75);
										Blend(t3,col,(right*0.8+z*0.4)*0.5);
										if(LEVEL_Read(blocks,xo,yo,z+1,0,0) == 78)
											Blend(t4,RGBA(255,255,255,255),(top*0.8+z*0.4)*0.8);
										else
											Blend(t4,col,(top*0.8+z*0.4)*0.8);
									}
								}
							}
						}
					}
					if (t1[3] < 255) lev->curH[rrx] = rry+1;
				}
			}
		}
		memcpy(lev->minH, lev->curH, map->width * sizeof *lev->minH);
		break;
	case OBLIQUE:
		if (lev->minH == NULL) {
			lev->minH = malloc(sizeof *lev->minH * map->width * 2);
			lev->curH = lev->minH + map->width;
			for (i = 0; i < map->width; i ++)
				lev->minH[i] = lev->curH[i] = map->height-1;
		}
		for (z = 128; z > -1; z--)
		{
			uint8_t waste[4];
			int dir = opt.flip == 1, inc = dir*2-1, xr, yr;
			for (xr = 15-dir*15; xr != dir*17-1; xr += inc)
			{
				for (yr = 0; yr < 16; yr++)
				{
					int xo,yo,ff,rr,f,t,xm,ym;

					if (opt.rotate) xo = yr, yo = xr;
					else            xo = xr, yo = yr;

					x = xr; y = yr; ff = 1;
					if (opt.flip == 1) {
						x = 15-xr;
						// y = 15-yo;
						ff = -1;
					}

					xm = y+lev->posx;
					ym = 128-z+x+lev->posy;
					if (ym > lev->minH[xm]) continue;
					rr = LEVEL_Read(blocks,xo,yo,z,0,0);
					if (rr == 0) {
						lev->curH[xm] = ym+1;
						continue;
					}
					t  = LEVEL_IsBlock(blocks,xo,yo,z+1);
					if (opt.rotate == 1)
						f = LEVEL_IsBlock(blocks,xo,yo+inc,z);
					else
						f = LEVEL_IsBlock(blocks,xo+inc,yo,z);

					DATA8 t1, t2;
					int   here,top,front;
					t1 = &map->bitmap[(xm+ym*map->width)*4];
					t2 = z > 1 ? t1 + map->stride : waste;

					top = here = front = 128;
					if (opt.exclude == 0 && !opt.cave)
					{
						switch (opt.daynight) {
						case 0:
							here  = LEVEL_GetLight(lev,xo,yo,z,1,1)*4;
							top   = LEVEL_GetLight(lev,xo,yo,z+1,1,1)*4;
							front = LEVEL_GetLight(lev,xo+ff*(opt.rotate != 1),yo+ff*(opt.rotate == 1),z,1,1)*4;
							break;
						case 1:
							here  = LEVEL_GetLight(lev,xo,yo,z,0.5,1)*4;
							top   = LEVEL_GetLight(lev,xo,yo,z+1,0.5,1)*4;
							front = LEVEL_GetLight(lev,xo+ff*(opt.rotate != 1),yo+ff*(opt.rotate == 1),z,0.5,1)*4;
							break;
						case 2:
							here  = LEVEL_GetLight(lev,xo,yo,z,0,1)*4;
							top   = LEVEL_GetLight(lev,xo,yo,z+1,0,1)*4;
							front = LEVEL_GetLight(lev,xo+ff*(opt.rotate != 1),yo+ff*(opt.rotate == 1),z,0,1)*4;
							break;
						}
					}

					if (opt.cave)
					{
						ULONG B;

						int blight = LEVEL_Read(blocklight,xo,yo,z,0,0);
						if (blight > 0) blight++;

						if (LEVEL_Read(skylight,xo,yo,z,15,1) == 0 && LEVEL_IsBlock(blocks,xo,yo,z))
							blight = 1;

						if (LEVEL_Read(skylight,xo,yo,z,15,1) != 0 || rr == 8 || rr == 9 || blight == 0)
							continue;

						int zzz = 128-z;
						int rt = (zzz > 0)*(zzz <= 32)*255 + (zzz > 32)*(zzz < 64)*(32-(zzz-32))*8;
						int gt = (zzz > 96)*(zzz < 128)*(32-(zzz-96))*8 + (zzz > 0)*(zzz < 32)*(zzz)*8 + (zzz >= 32)*(zzz <= 96)*255;
						int bt = (zzz >= 96)*(zzz < 128)*255 + (zzz > 64)*(zzz < 96)*(zzz-64)*8;
						B = RGBA(rt, gt, bt, 255);

						Blend(t1,B,(4*blight + 128));
						Blend(t2,B,(4*blight + 128)*0.5);
					}
					else /* !opt.cave */
					{
						// XXX (t || f) seems to exclude way too many blocks (TP)
						if (rr != 78 /*&& (t || f || opt.exclude != 0)*/ && (opt.exclude == 0 || rr == opt.exclude))
						{
							if(rr == 8 || rr == 9 || rr == 79) /* Water, Ice */
							{
								ULONG P = LEVEL_GetColor(lev, rr, 0);
								if (LEVEL_Read(blocks,xo,yo,z+1,0,0) == 0)
									Blend(t1, RGB_SETA(P, 128), top);
							}
							else if (rr == 6 || rr == 75 || rr == 76 || rr == 37 || rr == 38 || rr == 39 || rr == 40 ||
									 rr == 51 || rr == 50 || rr == 59 || rr == 63 || rr == 65 || rr == 66)
							{
								if (rr == 50 || rr == 51 || rr == 76)
									Blend(t2, LEVEL_GetColor(lev, rr, 0), 128);
								else
									Blend(t2, LEVEL_GetColor(lev, rr, 0), here*0.8+z*0.4);
							}
							else if (rr == 10 || rr == 11) /* Lava */
							{
								Blend(t1, LEVEL_GetColor(lev, rr, 0), 128);
								Blend(t2, LEVEL_GetColor(lev, rr, 0), 128*0.5);
							}
							else if (rr == 2) /* Grass */
							{
								if (LEVEL_Read(blocks,xo,yo,z+1,0,0) == 78)
									Blend(t1,RGBA(255,255,255,255),top*0.8+z*0.4);
								else
									Blend(t1,LEVEL_GetColor(lev,2,0),top*0.8+z*0.4);
								Blend(t2,LEVEL_GetColor(lev,3,0),(front*0.8+z*0.4)*0.75);
							}
							else /* Normal block */
							{
								ULONG c = LEVEL_GetColor(lev,rr,LEVEL_Read(lev->data,xo,yo,z,0,1));
								if (LEVEL_Read(blocks,xo,yo,z+1,0,0) == 78)
									Blend(t1,RGBA(255,255,255,255),top*0.8+z*0.4);
								else
									Blend(t1,c,top*0.8+z*0.4);
								Blend(t2,c,(front*0.8+z*0.4)*0.75);
							}
						}
					}
					if (t1[3] < 255) lev->curH[xm] = ym+1;
				}
			}
		}
		memcpy(lev->minH, lev->curH, map->width * sizeof *lev->minH);
		break;
	case ISOMETRIC:
		for (z = 128; z > -1; z--)
		{
			int xr, yr, dir = opt.rotate && !opt.flip;
			for (xr = 0; xr < 16; xr++)
			{
				for (yr = (1-dir)*15; yr != dir*17-1; yr += dir*2-1)
				{
					int xo,yo,ff = -1,ff2 = 1;

					if (opt.rotate) {
						if (!opt.flip) {
							yo =  yr;
							xo =  15-xr;
							ff = -1;
							ff2 = -1;
						} else {
							ff = 1;
							xo = xr;
						}
					} else {
						xo = 15 - xr;
					}

					if (opt.flip) {
						if (!opt.rotate) {
							xo = xr;
							yo = 15 - yr;
							ff = 1;
							ff2 = 1;
						} else {
							yo =  yr;
							ff2 = -1;
						}
					} else {
						yo = 15 - yr;
					}

					x = xr; y = yr;
					if (dir) y = 15 - yr;

					int rr = LEVEL_Read(blocks,xo,yo,z,0,0);
					int f  = LEVEL_IsBlock(blocks,xo-ff,yo,z);
					int t  = LEVEL_IsBlock(blocks,xo,yo,z+1);
					int r  = LEVEL_IsBlock(blocks,xo,yo-ff2,z);

					int rrx = (16+y-x);
					int rry = (y+x)*2;

					DATA8 t1, t2, t3, t4, t5, t6, t7, t8, t12, t13, t15, t16; //,t9, t10

					t1  = &map->bitmap[((rry+lev->posx)+((256-z*2)+rrx+lev->posy)*map->width)*4];
					t2  = t1 + map->stride;
					t3  = t2 + 4;
					t4  = t1 + 4;
					t5  = t2 - 4;
					t6  = t3 + 4;
					t7  = t1 - 4;
					t8  = t4 + 4;
					t15 = t2 + map->stride;
					t12 = t15 - 4;
					t16 = t15 + 4;
					t13 = t16 + 4;
					//t10 = t1 + 8 - map->stride
					//t9  = t7 - map->stride

					int here = 128, top = 128, left = 128, right = 128;
					if (opt.exclude == 0 && !opt.cave) {
						switch (opt.daynight) {
						case 0:
							here = LEVEL_GetLight(lev,xo,yo,z,1,1)*4;
							top = LEVEL_GetLight(lev,xo,yo,z+1,1,1)*4;
							left = LEVEL_GetLight(lev,xo-ff,yo,z,1,1)*4;
							right = LEVEL_GetLight(lev,xo,yo-ff2,z,1,1)*4;
							break;
						case(1):
							here = LEVEL_GetLight(lev,xo,yo,z,0.5,1)*4;
							top = LEVEL_GetLight(lev,xo,yo,z+1,0.5,1)*4;
							left = LEVEL_GetLight(lev,xo-ff,yo,z,0.5,1)*4;
							right = LEVEL_GetLight(lev,xo,yo-ff2,z,0.5,1)*4;
							break;
						case(2):
							here = LEVEL_GetLight(lev,xo,yo,z,0,1)*4;
							top = LEVEL_GetLight(lev,xo,yo,z+1,0,1)*4;
							left = LEVEL_GetLight(lev,xo-ff,yo,z,0,1)*4;
							right = LEVEL_GetLight(lev,xo,yo-ff2,z,0,1)*4;
						}
					}

					if (opt.cave)
					{
						int blight = LEVEL_Read(blocklight,xo,yo,z,0,1);
						if(blight > 0) blight++;

						if (LEVEL_Read(skylight,xo,yo,z,15,1) == 0 && LEVEL_IsBlock(blocks,xo,yo,z) && blight == 0)
							blight = 1;

						if (LEVEL_Read(skylight,xo,yo,z,15,1) != 0 || rr == 8 || rr == 9 || rr == 79 || blight == 0)
							continue;

						int bbb = 128-(blight*8);

						int rt = (bbb > 0)*(bbb <= 32)*255 + (bbb > 32)*(bbb < 64)*(32-(bbb-32))*8;
						int gt = (bbb > 96)*(bbb < 128)*(32-(bbb-96))*8 + (bbb > 0)*(bbb < 32)*(bbb)*8 + (bbb >= 32)*(bbb <= 96)*255;
						int bt = (bbb >= 96)*(bbb < 128)*255 + (bbb > 64)*(bbb < 96)*(bbb-64)*8;

						if (rr == 10 || rr == 11)
							rt = gt = 255, bt = 0;

						ULONG B = RGBA(rt*2, gt*2, bt*2, 255);
						int   zop = z*0.5 + 64;

						Blend(t1,B,(zop));
						Blend(t2,B,(zop)*0.5);
						Blend(t3,B,(zop)*0.65);
						Blend(t4,B,(zop)*0.8);
						Blend(t5,B,(zop)*0.6);
						Blend(t6,B,(zop)*0.55);
						Blend(t12,B,(zop)*0.5);
						Blend(t13,B,(zop)*0.65);
						Blend(t15,B,(zop)*0.6);
						Blend(t16,B,(zop)*0.55);
					}
					else /* !opt.cave */
					{
						if (rr != 0 && rr != 78 && (f || t ||  r || opt.exclude != 0) && (opt.exclude == 0 || rr == opt.exclude))
						{
							if (rr == 76 || rr == 75 || rr == 6 || rr == 37 || rr == 38 || rr == 39 || rr == 40 || rr == 51 ||
							    rr == 50 || rr == 59 || rr == 63 || rr == 65 || rr == 66)
							{
								if (rr == 37 || rr == 38 || rr == 39 || rr == 40)  /* Flowers, Mush */
								{
									Blend(t1,LEVEL_GetColor(lev,rr,0),here*0.8+z*0.4);
									Blend(t4,LEVEL_GetColor(lev,rr,0),here*0.8+z*0.4);
									Blend(t2,LEVEL_GetColor(lev,rr,0),here*0.8+z*0.4);
									Blend(t3,LEVEL_GetColor(lev,rr,0),here*0.8+z*0.4);
								}
								else if (rr == 50 || rr == 76)  /* Torches */
								{
									Blend(t1,LEVEL_GetColor(lev,rr,0),128);
									Blend(t4,LEVEL_GetColor(lev,rr,0),128);
									Blend(t2,LEVEL_GetColor(lev,5,0),128);
									Blend(t3,LEVEL_GetColor(lev,5,0),128);
								}
								else if (rr == 51)  /* Fire */
								{
									if (rand() % 10 > 4) Blend(t1,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t7,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t2,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t3,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t5,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t6,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t12,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t13,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t15,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t16,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t4,LEVEL_GetColor(lev,rr,0),128);
									if (rand() % 10 > 4) Blend(t8,LEVEL_GetColor(lev,rr,0),128);
								}
								else
								{
									Blend(t1,LEVEL_GetColor(lev,rr,0),(here*0.8+z*0.4));
									Blend(t4,LEVEL_GetColor(lev,rr,0),(here*0.8+z*0.4)*0.9);
									Blend(t7,LEVEL_GetColor(lev,rr,0),(here*0.8+z*0.4)*0.85);
									Blend(t8,LEVEL_GetColor(lev,rr,0),(here*0.8+z*0.4)*0.95);
								}
							}
							else if (rr == 10 || rr == 11) /* Lava */
							{
								Blend(t1,LEVEL_GetColor(lev,rr,0),128);
								//Blend(t10,LEVEL_GetColor(lev,rr,0),128);
								Blend(t7,LEVEL_GetColor(lev,rr,0),128*0.95);
								Blend(t2,LEVEL_GetColor(lev,rr,0),128*0.5);
								Blend(t3,LEVEL_GetColor(lev,rr,0),128*0.65);
								Blend(t5,LEVEL_GetColor(lev,rr,0),128*0.6);
								Blend(t6,LEVEL_GetColor(lev,rr,0),128*0.55);
								Blend(t12,LEVEL_GetColor(lev,rr,0),128*0.5);
								Blend(t13,LEVEL_GetColor(lev,rr,0),128*0.65);
								Blend(t15,LEVEL_GetColor(lev,rr,0),128*0.6);
								Blend(t16,LEVEL_GetColor(lev,rr,0),128*0.55);
								Blend(t4,LEVEL_GetColor(lev,rr,0),128*0.8);
								//Blend(t9,LEVEL_GetColor(lev,rr,0),128*0.8);
								Blend(t8,LEVEL_GetColor(lev,rr,0),128*0.85);
							}
							else if (rr == 8 || rr == 9 || rr == 79)  /* Water, Ice */
							{
								ULONG P = LEVEL_GetColor(lev, rr,0);
								if (LEVEL_Read(blocks,xo,yo,z+1,0,0) == 0 ||
								    LEVEL_Read(blocks,xo+1,yo,z,-1,0) == 0 ||
								    LEVEL_Read(blocks,xo,yo+1,z,-1,0) == 0)
								{
									P = RGB_SETA(P, 128);
									Blend(t1,P,(here*0.8+z*0.4));
									Blend(t2,P,(here*0.8+z*0.4)*0.65);
									Blend(t3,P,(here*0.8+z*0.4)*0.5);
									Blend(t5,P,(here*0.8+z*0.4)*0.55);
									Blend(t6,P,(here*0.8+z*0.4)*0.6);
									Blend(t4,P,(here*0.8+z*0.4)*0.8);
									Blend(t7,P,(here*0.8+z*0.4)*0.95);
									Blend(t8,P,(here*0.8+z*0.4)*0.85);
								}
							}
							else if (rr == 2) /* Grass */
							{
								if (LEVEL_Read(blocks,xo,yo,z+1,0,1) == 78) /* Snow */
								{
									Blend(t1,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4));
									Blend(t7,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4)*0.95);
								}
								else
								{
									Blend(t1,LEVEL_GetColor(lev,2,0),(top*0.8+z*0.4));
									//Blend(t10,LEVEL_GetColor(lev,2),(top*0.8+z*0.4));
									Blend(t7,LEVEL_GetColor(lev,2,0),(top*0.8+z*0.4)*0.95);
								}

								Blend(t2,LEVEL_GetColor(lev,1003,0),(left*0.8+z*0.4)*0.65);
								Blend(t3,LEVEL_GetColor(lev,1003,0),(right*0.8+z*0.4)*0.5);
								Blend(t5,LEVEL_GetColor(lev,1003,0),(left*0.8+z*0.4)*0.6);
								Blend(t6,LEVEL_GetColor(lev,1003,0),(right*0.8+z*0.4)*0.55);
								Blend(t12,LEVEL_GetColor(lev,1003,0),(left*0.8+z*0.4)*0.65);
								Blend(t13,LEVEL_GetColor(lev,1003,0),(right*0.8+z*0.4)*0.5);
								Blend(t15,LEVEL_GetColor(lev,1003,0),(left*0.8+z*0.4)*0.6);
								Blend(t16,LEVEL_GetColor(lev,1003,0),(right*0.8+z*0.4)*0.55);
								if (LEVEL_Read(blocks,xo,yo,z+1,0,1) == 78)
								{
									Blend(t4,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4)*0.8);
									//Blend(t9,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4)*0.8);
									Blend(t8,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4)*0.85);
								}
								else
								{
									Blend(t4,LEVEL_GetColor(lev,2,0),(top*0.8+z*0.4)*0.8);
									//Blend(t9,LEVEL_GetColor(lev,2,0),(top*0.8+z*0.4)*0.8);
									Blend(t8,LEVEL_GetColor(lev,2,0),(top*0.8+z*0.4)*0.85);
								}
							}
							else  /* Normal block */
							{
								int dat = LEVEL_Read(lev->data,xo,yo,z,0,1);
								if (LEVEL_Read(blocks,xo,yo,z+1,0,0) == 78){
									Blend(t1,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4));
									//Blend(t10,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4));
									Blend(t7,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4)*0.95);
								}
								else
								{
									Blend(t1,LEVEL_GetColor(lev,rr,dat),(top*0.8+z*0.4));
									//Blend(t10,LEVEL_GetColor(lev,rr,dat),(top*0.8+z*0.4));
									Blend(t7,LEVEL_GetColor(lev,rr,dat),(top*0.8+z*0.4)*0.95);
								}
								Blend(t2,LEVEL_GetColor(lev,rr,dat),(left*0.8+z*0.4)*0.75);
								Blend(t3,LEVEL_GetColor(lev,rr,dat),(right*0.8+z*0.4)*0.5);
								Blend(t5,LEVEL_GetColor(lev,rr,dat),(left*0.8+z*0.4)*0.6);
								Blend(t6,LEVEL_GetColor(lev,rr,dat),(right*0.8+z*0.4)*0.55);
								Blend(t12,LEVEL_GetColor(lev,rr,dat),(left*0.8+z*0.4)*0.75);
								Blend(t13,LEVEL_GetColor(lev,rr,dat),(right*0.8+z*0.4)*0.5);
								Blend(t15,LEVEL_GetColor(lev,rr,dat),(left*0.8+z*0.4)*0.6);
								Blend(t16,LEVEL_GetColor(lev,rr,dat),(right*0.8+z*0.4)*0.55);
								if (LEVEL_Read(blocks,xo,yo,z+1,0,0) == 78)
								{
									Blend(t4,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4)*0.8);
									//Blend(t9,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4)*0.8);
									Blend(t8,LEVEL_GetColor(lev,80,0),(top*0.8+z*0.4)*0.85);
								}
								else
								{
									Blend(t4,LEVEL_GetColor(lev,rr,dat),(top*0.8+z*0.4)*0.8);
									//Blend(t9,LEVEL_GetColor(lev,rr,dat),(top*0.8+z*0.4)*0.8);
									Blend(t8,LEVEL_GetColor(lev,rr,dat),(top*0.8+z*0.4)*0.85);
								}
							}
						}
					}
				}
			}
		}
	}
}

void LEVEL_DumpStat(Level lev, STRPTR name)
{
	TEXT path[256];
	CopyString(path, BaseName(name), sizeof path);
	AddExt(path, "txt", sizeof path, False);

	FILE * out = fopen(path, "w");

	if (out)
	{
		fprintf(out, "Map surface is: %lu square meters (%g km\xC2\xB2)\n", opt.total * 256UL, opt.total * 0.000256);

		fprintf(out, "Block amounts:\n");
		fprintf(out, "Air:        %lu\n", lev->count[1]);
		fprintf(out, "Grass:      %lu\n", lev->count[2]);
		fprintf(out, "Dirt:       %lu\n", lev->count[3]);
		fprintf(out, "Snow:       %lu\n\n", lev->count[78]);

		fprintf(out, "Water:      %lu\n", lev->count[8]+lev->count[9]);
		fprintf(out, "Ice:        %lu\n", lev->count[79]);
		fprintf(out, "Lava:       %lu\n", lev->count[10]+lev->count[11]);
		fprintf(out, "Obisidian:  %lu\n\n", lev->count[49]);

		fprintf(out, "Trunk:      %lu\n", lev->count[17]);
		fprintf(out, "Leaves:     %lu\n", lev->count[18]);
		fprintf(out, "Wood:       %lu\n", lev->count[5]);
		fprintf(out, "Cactus:     %lu\n\n", lev->count[81]);

		fprintf(out, "Sand:       %lu\n", lev->count[12]);
		fprintf(out, "Gravel:     %lu\n", lev->count[13]);
		fprintf(out, "Clay:       %lu\n\n", lev->count[82]);

		fprintf(out, "Gold Ore:   %lu\n", lev->count[14]);
		fprintf(out, "Iron Ore:   %lu\n", lev->count[15]);
		fprintf(out, "Coal Ore:   %lu\n", lev->count[16]);
		fprintf(out, "Lapis Ore:  %lu\n", lev->count[21]);
		fprintf(out, "Diamonds:   %lu\n", lev->count[56]);
		fprintf(out, "Redstone:   %lu\n\n", lev->count[73]+lev->count[74]);

		fprintf(out, "Cobble:     %lu\n", lev->count[4]);
		fprintf(out, "Glass:      %lu\n", lev->count[20]);
		fprintf(out, "Cloth:      %lu\n", lev->count[35]);
		fprintf(out, "Gold block: %lu\n", lev->count[41]);
		fprintf(out, "Iron block: %lu\n", lev->count[42]);
		fprintf(out, "Diam block: %lu\n", lev->count[57]);
		fprintf(out, "Lapis block:%lu\n\n", lev->count[22]);

		fprintf(out, "Farmland:   %lu\n", lev->count[60]);
		fprintf(out, "Crops:      %lu\n", lev->count[59]);
		fprintf(out, "Sugar cane: %lu\n", lev->count[83]);
		fprintf(out, "Torch:      %lu\n", lev->count[50]);
		fprintf(out, "Workbench:  %lu\n", lev->count[58]);
		fprintf(out, "Chest:      %lu\n", lev->count[54]);
		fprintf(out, "Furnace:    %lu\n", lev->count[61]+lev->count[62]);
		fprintf(out, "Doors:      %lu\n", lev->count[64]/2 + lev->count[71] / 2);
		fprintf(out, "Signs:      %lu\n", lev->count[63]+lev->count[68]);
		fprintf(out, "Ladder:     %lu\n", lev->count[65]);
		fprintf(out, "Railtracks: %lu\n", lev->count[66]);
		fprintf(out, "Stairs:     %lu\n", lev->count[53]+lev->count[67]);
		fprintf(out, "Lever:      %lu\n", lev->count[69]);
		fprintf(out, "Buttons:    %lu\n", lev->count[77]);
		fprintf(out, "Plates:     %lu\n", lev->count[70]+lev->count[72]);
		fprintf(out, "RS Wire:    %lu\n", lev->count[55]);
		fprintf(out, "RS Torch:   %lu\n\n", lev->count[75]+lev->count[76]);

		fprintf(out, "Zombies:    %lu\n", lev->mobs[ZOMBIE]);
		fprintf(out, "Cows:       %lu\n", lev->mobs[COW]);
		fprintf(out, "Sheeps:     %lu\n", lev->mobs[SHEEP]);
		fprintf(out, "Pigs:       %lu\n", lev->mobs[PIG]);
		fprintf(out, "Skeletons:  %lu\n", lev->mobs[SKELETON]);
		fprintf(out, "Creepers:   %lu\n", lev->mobs[CREEPER]);
		fprintf(out, "Slimes:     %lu\n", lev->mobs[SLIME]);
		fprintf(out, "Squid:      %lu\n", lev->mobs[SQUID]);
		fclose(out);
	}
}

static void LEVEL_FindCoord(NBT root, int * coord)
{
	int n;
	if (root && root->type == TAG_Compound)
	{
		for (root = HEAD(root->child); root; NEXT(root))
			if (root->type == TAG_Int && (n = FindInList("x,y,z", root->name, 0)) >= 0)
				coord[n] = root->val.dword;
	}
}

Bool LEVEL_DumpStuff(NBT chest)
{
	if (chest && chest->type == TAG_List_Compound)
	{
		int i, first;
		for (i = 0, first = 1; i < chest->len; i ++)
		{
			NBT root = chest->val.array[i];
			NBT id   = NBT_FindNode(root, "id");
			int item = -1;
			int coord[3] = {};
			STRPTR name = NULL;

			if (id && id->type == TAG_String)
				item = FindInList(
					"MobSpawner,Chest,Furnace,Item,Sheep,"
					"Chicken,Cow,Pig,Squid,Slime,Zombie,Creeper,"
					"Skeleton,Sign,Painting,Minecart,FallingSand,"
					"PrimedTnt",
					name = id->val.string, 0
				);

			switch (item) {
			case -1: return False;
			case 0: // MobSpawner
				LEVEL_FindCoord(root, coord);
				fprintf(stdout, "- %s spawner at x = %d, y = %d, z = %d\n", NBT_ToString(root, "EntityId", "Unknown"),
					coord[0], coord[1], coord[2]);
				break;
			case 1: // Chest
			case 2: // Furnace
				id = NBT_FindNode(root, "Items");

				if (id && id->type == TAG_List_Compound)
				{
					int j, first;
					for (j = 0, first = 1; j < id->len; j ++)
					{
						NBT item = id->val.array[j];
						if (first)
						{
							LEVEL_FindCoord(root, coord);
							first = 0;
							fprintf(stdout, "- %s at x = %d, y = %d, z = %d:\n", name, coord[0], coord[1], coord[2]);
						}
						fprintf(stdout, "  - %d %s\n", NBT_ToInt(item, "Count", -1),
							NBT_FindItemName(NBT_ToInt(item, "id", -1)));
					}
				}
				else return True; // Empty furnace
				break;
			case 3: // Item
				NBT_ConvertToInt(NBT_FindNode(root, "Pos"), coord, DIM(coord));
				root = NBT_FindNode(root, "Item");
				fprintf(stdout, "- %d %s at x = %d, y = %d, z = %d\n", NBT_ToInt(root, "Count", 0),
					NBT_FindItemName(NBT_ToInt(root, "id", -1)), coord[0], coord[1], coord[2]);
				break;
			case 4: // Sheep
				NBT_ConvertToInt(NBT_FindNode(root, "Pos"), coord, DIM(coord));
				fprintf(stdout, "- %s sheep at x = %d, y = %d, z = %d\n", NBT_WoolColor(NBT_ToInt(root, "Color", 0)),
					coord[0], coord[1], coord[2]);
				break;
			case 5: // Chicken
			case 6: // Cow
			case 7: // Pig
			case 8: // Squid
			case 9: // Slime
			case 10: // Zombie
			case 11: // Creeper
			case 12: // Skeleton
				NBT_ConvertToInt(NBT_FindNode(root, "Pos"), coord, DIM(coord));
				fprintf(stdout, "- %s at x = %d, y = %d, z = %d\n", name, coord[0], coord[1], coord[2]);
			// Id above are ignored
			}
		}
	}
	return True;
}

void LEVEL_Render(APTR to)
{
	Level    lev;
	Chunk    list, next;
	ListHead files = {};
	int      rect[8], i;
	Image    img;
	ULONG    lastUpd = 0, curMS;

	MutexEnter(sync);
	LEVEL_GetFiles(&files, opt.world);
	opt.total = files.lh_Count;
	fprintf(stderr, "%ld files loaded\n", files.lh_Count);
	LEVEL_GetRect(&files, rect);
	img = opt.image = LEVEL_AllocImage(rect);
	if (img == NULL || !img->bitmap)
	{
		SIT_Log(SIT_ERROR, "Fail to alloc image :-(");
		while ((list = (APTR) ListRemHead(&files)))
			free(list);
		opt.cancel = 1;
		SIT_Notify(opt.notify, (APTR) 2);
		return;
	}
	lev = LEVEL_Init();
	if (opt.mode >= OBLIQUE)
		ListSort(&files, LEVEL_SortFiles);

	/* Notify beginning of rendering */
	SIT_Notify(opt.notify, NULL);

	for (list = next = HEAD(files), i = 0; list && ! opt.cancel; list = next, i ++)
	{
		NEXT(next);
		NBT chunk = list->offset > 0 ? NBT_GetFromRegion(opt.world, list->file, list->offset) : NBT_Parse(list->file);

		#if 1
		lev->posx = (list->x - rect[0]) * 16;
		lev->posy = (list->y - rect[1]) * 16;

		int xx, yy;
		switch (opt.mode) {
		case OBLIQUE_ANGLED:
		case ISOMETRIC:
			xx = list->x*16;
			yy = list->y*16;
			if (opt.flip == 1)
				yy = abs((list->y)-(rect[3]-rect[1]))*16;
			if (opt.rotate == 0)
				xx = abs((list->x)-(rect[2]-rect[0]))*16;

			if (opt.mode == OBLIQUE_ANGLED)
			{
				lev->posx = xx-yy-rect[4]*16;
				lev->posy = xx+yy-rect[5]*16 + 144;
			}
			else
			{
				lev->posx = (xx-yy-rect[4]*16)*2 + 32;
				lev->posy = (xx+yy-rect[5]*16)+16;
			}
			break;
		case OBLIQUE:
			if (opt.flip == 1) {
				if (opt.rotate == 0) {
					//lev->posx = (lev->posx-rect[0])*16;
					lev->posy = abs((lev->posy-rect[1])-(rect[3]-rect[1]))*16;
				} else {
					xx = lev->posx; yy = lev->posy;
					lev->posx = (yy-rect[1])*16;
					lev->posy = abs((xx-rect[0])-(rect[2]-rect[0]))*16;
				}
			} else {
				if (opt.rotate == 1) {
					xx = lev->posx; yy = lev->posy;
					lev->posx = (yy-rect[1])*16;
					lev->posy = (xx-rect[0])*16;
				}
			}
		}

		LEVEL_LoadFrom(lev, chunk, 0, img);
		NBT_Free(chunk);
		fprintf(stderr, "%d / %d\r", i, opt.total);
		#else
		if (chunk)
		{
			NBT stuff = NBT_FindNode(chunk, "TileEntities");

			if (! LEVEL_DumpStuff(stuff))
				NBT_Dump(chunk, 0);

		/*	stuff = NBT_FindNode(chunk, "Entities");

			if (! LEVEL_DumpStuff(stuff))
				NBT_Dump(chunk, 0);

			NBT_Free(chunk); */
		}
		#endif
		opt.cur ++;
		curMS = TimeMS();
		if (lastUpd != (curMS & ~255))  {
			lastUpd = curMS & ~255;
			SIT_Notify(opt.notify, (APTR) 1);
		}
		free(ListRemHead(&files));
	}
	MutexLeave(sync);
	/* 'img' will be freed by the callback */
	if (lev->minH) free(lev->minH), lev->minH = NULL;
	LEVEL_DumpStat(lev, opt.name);
	SIT_Notify(opt.notify, (APTR) 2);
}

